import {
  canUse,
  emtpyToUndefined,
  isNull,
  PagedList,
  PageParams,
  DurationParams,
  isUndefined,
  numberToString,
} from "mmb";
import * as messagepb from "../grpc-client/shared/message_pb";
import * as userpb from "../grpc-client/service/user_pb";
import UserRole from "../bg-enums/userRole";
import {
  getBgsUserOpSafely,
  getMetadata,
  getOptionsOfLight,
  wrapperGrpcError,
} from "../grpc-client/grpcAdapter";
import GrpcErrCode from "../grpc-client/grpcErrCode";
import { numberZeroValue, pbTimestampToJs, jsTimestampToPb } from "./helper";
import {
  User,
  CreateUserParams,
  UpdateInfoParams,
  UpdateMobileParams,
  UpdateMemberTokenParams,
  UserExt,
  UpsertExtParams,
  UserStat,
  UpsertStatParams,
  UserWithStat,
  UserAccount,
  UserAccountWithdrawApplyment,
  CreateUserWithdrawApplymentParams,
  AccountLogInfo,
  AggrUserAccountLogsToInOutRet,
} from "./declareTypes";
import WithdrawTarget from "../bg-enums/withdrawTarget";

/****************************************************************************************/
/*                                   user entity                                        */
/****************************************************************************************/

const userPbToObject = (pb: userpb.UserEntity): User => {
  return {
    userId: `${pb.getId()}`,
    openId: pb.getOpenId(),
    mobile: pb.getMobile(),
    memberToken: emtpyToUndefined(pb.getMemberToken()),
    memberTokenRefreshed: pbTimestampToJs(pb.getMemberTokenRefreshed()),
    pic: pb.getPic(),
    nickName: pb.getNickName(),
    realName: pb.getRealName(),
    idCardNo: pb.getIdCardNo(),
    intro: pb.getIntro(),
    homepageBg: pb.getHomepageBg(),
    registered: pbTimestampToJs(pb.getRegistered()),
    unionId: pb.getUnionId(),
    roles: pb.getRolesList().map((v) => v as UserRole),
  };
};

/****************************************************************************************/
/*                                   user cmd                                           */
/****************************************************************************************/

export const createUser = async ({
  openId,
  nickName,
  registerPage,
  unionId,
}: CreateUserParams): Promise<User> => {
  const cl = getBgsUserOpSafely();
  const req = new userpb.CreateUserRequest();

  req.setOpenId(openId);
  req.setNickName(nickName);
  req.setRegisterPage(registerPage);
  req.setUnionId(unionId);
  req.setRolesList([UserRole.NORMAL_USER]);

  return new Promise((resolve, reject) => {
    cl.createUser(req, getMetadata(), getOptionsOfLight(), (err, res) => {
      if (!isNull(err)) {
        reject(wrapperGrpcError(err.details));
        return;
      }
      const opRet = res.getOpRet()!;
      if (!opRet.getSuccess()) {
        reject(wrapperGrpcError(opRet.getMsg()));
        return;
      }
      resolve(userPbToObject(res.getUser()!));
    });
  });
};

export const UpdateInfo = async ({
  userId,
  pic,
  nickName,
  intro,
  homepageBg,
}: UpdateInfoParams): Promise<User> => {
  const cl = getBgsUserOpSafely();
  const req = new userpb.UpdateUserInfoRequest();

  req.setId(Number(userId));
  if (!isUndefined(nickName)) {
    req.setNickName(nickName);
  }
  if (!isUndefined(pic)) {
    req.setPic(pic);
  }
  if (!isUndefined(intro)) {
    req.setIntro(intro);
  }
  if (!isUndefined(homepageBg)) {
    req.setHomepageBg(homepageBg);
  }

  return new Promise((resolve, reject) => {
    cl.updateUserInfo(req, getMetadata(), getOptionsOfLight(), (err, res) => {
      if (!isNull(err)) {
        reject(wrapperGrpcError(err.details));
        return;
      }
      const opRet = res.getOpRet()!;
      if (!opRet.getSuccess()) {
        reject(wrapperGrpcError(opRet.getMsg()));
        return;
      }
      resolve(userPbToObject(res.getUser()!));
    });
  });
};

export const UpdateMobile = async ({
  userId,
  mobile,
}: UpdateMobileParams): Promise<User> => {
  const cl = getBgsUserOpSafely();
  const req = new userpb.UpdateUserMobileRequest();

  req.setId(Number(userId));
  req.setMobile(mobile);

  return new Promise((resolve, reject) => {
    cl.updateUserMobile(req, getMetadata(), getOptionsOfLight(), (err, res) => {
      if (!isNull(err)) {
        reject(wrapperGrpcError(err.details));
        return;
      }
      const opRet = res.getOpRet()!;
      if (!opRet.getSuccess()) {
        reject(wrapperGrpcError(opRet.getMsg()));
        return;
      }
      resolve(userPbToObject(res.getUser()!));
    });
  });
};

export const updateMemberToken = async ({
  userId,
  memberToken,
}: UpdateMemberTokenParams): Promise<User> => {
  const cl = getBgsUserOpSafely();
  const req = new userpb.UpdateUserMemberTokenRequest();

  req.setId(Number(userId));
  req.setMemberToken(memberToken);

  return new Promise((resolve, reject) => {
    cl.updateUserMemberToken(
      req,
      getMetadata(),
      getOptionsOfLight(),
      (err, res) => {
        if (!isNull(err)) {
          reject(wrapperGrpcError(err.details));
          return;
        }
        const opRet = res.getOpRet()!;
        if (!opRet.getSuccess()) {
          reject(wrapperGrpcError(opRet.getMsg()));
          return;
        }
        resolve(userPbToObject(res.getUser()!));
      }
    );
  });
};

/****************************************************************************************/
/*                                   user query                                         */
/****************************************************************************************/

export const getUser = async (userId: string): Promise<User> => {
  const cl = getBgsUserOpSafely();
  const req = new userpb.GetUserRequest();

  req.setId(Number(userId));

  return new Promise((resolve, reject) => {
    cl.getUser(req, getMetadata(), getOptionsOfLight(), (err, res) => {
      if (!isNull(err)) {
        reject(wrapperGrpcError(err.details));
        return;
      }
      const opRet = res.getOpRet()!;
      if (!opRet.getSuccess()) {
        reject(wrapperGrpcError(opRet.getMsg()));
        return;
      }
      resolve(userPbToObject(res.getUser()!));
    });
  });
};

export const getUserByOpenIDSafely = async (openId: string): Promise<User> => {
  const v = await getUserByOpenID(openId);
  return v!;
};

export const getUserByOpenID = async (
  openId: string
): Promise<User | undefined> => {
  const cl = getBgsUserOpSafely();
  const req = new userpb.GetUserByOpenIDRequest();

  req.setOpenId(openId);

  return new Promise((resolve, reject) => {
    cl.getUserByOpenID(req, getMetadata(), getOptionsOfLight(), (err, res) => {
      if (!isNull(err)) {
        reject(wrapperGrpcError(err.details));
        return;
      }
      const opRet = res.getOpRet()!;
      if (!opRet.getSuccess()) {
        if (opRet.getCode() === GrpcErrCode.NOT_FOUND) {
          resolve(undefined);
          return;
        } else {
          reject(wrapperGrpcError(opRet.getMsg()));
          return;
        }
      }
      resolve(userPbToObject(res.getUser()!));
    });
  });
};

export const getUserByMobile = async (
  mobile: string
): Promise<User | undefined> => {
  const cl = getBgsUserOpSafely();
  const req = new userpb.GetUserByMobileRequest();

  req.setMobile(mobile);

  return new Promise((resolve, reject) => {
    cl.getUserByMobile(req, getMetadata(), getOptionsOfLight(), (err, res) => {
      if (!isNull(err)) {
        reject(wrapperGrpcError(err.details));
        return;
      }
      const opRet = res.getOpRet()!;
      if (!opRet.getSuccess()) {
        if (opRet.getCode() === GrpcErrCode.NOT_FOUND) {
          resolve(undefined);
          return;
        } else {
          reject(wrapperGrpcError(opRet.getMsg()));
          return;
        }
      }
      resolve(userPbToObject(res.getUser()!));
    });
  });
};

export const listUsers = async (
  pager: PageParams
): Promise<PagedList<User>> => {
  const cl = getBgsUserOpSafely();
  const req = new userpb.ListUsersRequest();

  req.setLimit(pager.limit);
  req.setOffset(pager.offset);

  return new Promise((resolve, reject) => {
    cl.listUsers(req, getMetadata(), getOptionsOfLight(), (err, res) => {
      if (!isNull(err)) {
        reject(wrapperGrpcError(err.details));
        return;
      }
      const opRet = res.getOpRet()!;
      if (!opRet.getSuccess()) {
        reject(wrapperGrpcError(opRet.getMsg()));
        return;
      }
      const ret: PagedList<User> = {
        total: 0,
        items: [],
      };
      ret.total = res.getTotal();
      ret.items = res.getUsersList().map((u) => userPbToObject(u));
      resolve(ret);
    });
  });
};

/****************************************************************************************/
/*                                   userExt entity                                     */
/****************************************************************************************/

const userExtPbToObject = (pb: userpb.UserExtEntity): UserExt => {
  return {
    userId: `${pb.getUserId()}`,
    shareUserId: numberToString(numberZeroValue(pb.getShareUserId())),
    sex: pb.getSex(),
    city: pb.getCity(),
    province: pb.getProvince(),
    country: pb.getCountry(),
  };
};

/****************************************************************************************/
/*                                   userExt cmd                                        */
/****************************************************************************************/

export const UpsertExt = async ({
  userId,
  shareUserId,
  sex,
  city,
  province,
  country,
}: UpsertExtParams): Promise<UserExt> => {
  const cl = getBgsUserOpSafely();
  const req = new userpb.UpsertUserExtRequest();

  req.setUserId(Number(userId));
  if (!isUndefined(shareUserId)) {
    req.setShareUserId(Number(shareUserId));
  }
  if (!isUndefined(sex)) {
    req.setSex(sex);
  }
  if (!isUndefined(city)) {
    req.setCity(city);
  }
  if (!isUndefined(province)) {
    req.setProvince(province);
  }
  if (!isUndefined(country)) {
    req.setCountry(country);
  }

  return new Promise((resolve, reject) => {
    cl.upsertUserExt(req, getMetadata(), getOptionsOfLight(), (err, res) => {
      if (!isNull(err)) {
        reject(wrapperGrpcError(err.details));
        return;
      }
      const opRet = res.getOpRet()!;
      if (!opRet.getSuccess()) {
        reject(wrapperGrpcError(opRet.getMsg()));
        return;
      }
      resolve(userExtPbToObject(res.getUserExt()!));
    });
  });
};

/****************************************************************************************/
/*                                   userExt query                                      */
/****************************************************************************************/

export const getUserExt = async (userId: string): Promise<UserExt> => {
  const cl = getBgsUserOpSafely();
  const req = new userpb.GetUserExtByUserIDRequest();

  req.setUserId(Number(userId));

  return new Promise((resolve, reject) => {
    cl.getUserExtByUserID(
      req,
      getMetadata(),
      getOptionsOfLight(),
      (err, res) => {
        if (!isNull(err)) {
          reject(wrapperGrpcError(err.details));
          return;
        }
        const opRet = res.getOpRet()!;
        if (!opRet.getSuccess()) {
          reject(wrapperGrpcError(opRet.getMsg()));
          return;
        }
        resolve(userExtPbToObject(res.getUserExt()!));
      }
    );
  });
};

/****************************************************************************************/
/*                                   userStat entity                                    */
/****************************************************************************************/

const userStatPbToObject = (pb: userpb.UserStatEntity): UserStat => {
  return {
    userId: `${pb.getUserId()}`,
    fansCount: pb.getFansCount(),
    followUserCount: pb.getFollowUserCount(),
    followBrandCount: pb.getFollowBrandCount(),
    endorseBrandCount: pb.getEndorseBrandCount(),
    endorseActivityCount: pb.getEndorseActivityCount(),
    involveActivityCount: pb.getInvolveActivityCount(),
    orderCount: pb.getOrderCount(),
    viewCount: pb.getViewCount(),
    payAmount: pb.getPayAmount(),
  };
};

/****************************************************************************************/
/*                                   userStat cmd                                       */
/****************************************************************************************/

export const UpsertStat = async ({
  userId,
  fansCount,
  followUserCount,
  followBrandCount,
  endorseBrandCount,
  endorseActivityCount,
  involveActivityCount,
  orderCount,
  viewCount,
  payAmount,
}: UpsertStatParams): Promise<UserStat> => {
  const cl = getBgsUserOpSafely();
  const req = new userpb.UpsertUserStatRequest();

  req.setUserId(Number(userId));
  if (!isUndefined(fansCount)) {
    req.setFansCount(fansCount);
  }
  if (!isUndefined(followUserCount)) {
    req.setFollowUserCount(followUserCount);
  }
  if (!isUndefined(followBrandCount)) {
    req.setFollowBrandCount(followBrandCount);
  }
  if (!isUndefined(endorseBrandCount)) {
    req.setEndorseBrandCount(endorseBrandCount);
  }
  if (!isUndefined(endorseActivityCount)) {
    req.setEndorseActivityCount(endorseActivityCount);
  }
  if (!isUndefined(involveActivityCount)) {
    req.setInvolveActivityCount(involveActivityCount);
  }
  if (!isUndefined(orderCount)) {
    req.setOrderCount(orderCount);
  }
  if (!isUndefined(viewCount)) {
    req.setViewCount(viewCount);
  }
  if (!isUndefined(payAmount)) {
    req.setPayAmount(payAmount);
  }

  return new Promise((resolve, reject) => {
    cl.upsertUserStat(req, getMetadata(), getOptionsOfLight(), (err, res) => {
      if (!isNull(err)) {
        reject(wrapperGrpcError(err.details));
        return;
      }
      const opRet = res.getOpRet()!;
      if (!opRet.getSuccess()) {
        reject(wrapperGrpcError(opRet.getMsg()));
        return;
      }
      resolve(userStatPbToObject(res.getUserStat()!));
    });
  });
};

/****************************************************************************************/
/*                                   user joins entity                                  */
/****************************************************************************************/

const userWithStatPbToObject = (
  pb: userpb.UserWithStatEntity
): UserWithStat => {
  return {
    userId: `${pb.getId()}`,
    openId: pb.getOpenId(),
    mobile: pb.getMobile(),
    memberToken: emtpyToUndefined(pb.getMemberToken()),
    memberTokenRefreshed: pbTimestampToJs(pb.getMemberTokenRefreshed()),
    pic: pb.getPic(),
    nickName: pb.getNickName(),
    intro: pb.getIntro(),
    homepageBg: pb.getHomepageBg(),
    registered: pbTimestampToJs(pb.getRegistered()),
    unionId: pb.getUnionId(),
    roles: pb.getRolesList().map((v) => v as UserRole),
    fansCount: pb.getFansCount(),
    followUserCount: pb.getFollowUserCount(),
    followBrandCount: pb.getFollowBrandCount(),
    followStoreCount: pb.getFollowStoreCount(),
    endorseBrandCount: pb.getEndorseBrandCount(),
    endorseStoreCount: pb.getEndorseStoreCount(),
    endorseActivityCount: pb.getEndorseActivityCount(),
    involveActivityCount: pb.getInvolveActivityCount(),
    orderCount: pb.getOrderCount(),
    viewCount: pb.getViewCount(),
    payAmount: pb.getPayAmount(),
  };
};

export const getUserWithStat = async (
  userId: string
): Promise<UserWithStat> => {
  const cl = getBgsUserOpSafely();
  const req = new userpb.GetUserWithStatRequest();

  req.setId(Number(userId));

  return new Promise((resolve, reject) => {
    cl.getUserWithStat(req, getMetadata(), getOptionsOfLight(), (err, res) => {
      if (!isNull(err)) {
        reject(wrapperGrpcError(err.details));
        return;
      }
      const opRet = res.getOpRet()!;
      if (!opRet.getSuccess()) {
        reject(wrapperGrpcError(opRet.getMsg()));
        return;
      }
      resolve(userWithStatPbToObject(res.getUserWithStat()!));
    });
  });
};

/****************************************************************************************/
/*                                   user account entity                                */
/****************************************************************************************/

const userAccountPbToObject = (pb: userpb.UserAccountEntity): UserAccount => {
  return {
    userId: `${pb.getUserId()}`,
    availableAmount: pb.getAvailableAmount(),
    pendingAmount: pb.getPendingAmount(),
    withdrawedAmount: pb.getWithdrawedAmount(),
  };
};

const userAccountWithdrawApplymentPbToObject = (
  pb: userpb.UserAccountWithdrawApplymentEntity
): UserAccountWithdrawApplyment => {
  return {
    userAccountWithdrawApplymentId: `${pb.getId()}`,
    userId: `${pb.getUserId()}`,
    target: pb.getTarget() as WithdrawTarget,
    applied: pbTimestampToJs(pb.getApplied())!,
    amount: pb.getAmount(),
    remark: pb.getRemark(),
    applyState: pb.getApplyState(),
  };
};

/****************************************************************************************/
/*                                   user account cmd                                   */
/****************************************************************************************/

export const createUserAccountWithdrawApplyment = async ({
  userId,
  target,
  amount,
  realName,
  idCardNo,
  remark,
}: CreateUserWithdrawApplymentParams): Promise<UserAccountWithdrawApplyment> => {
  const cl = getBgsUserOpSafely();
  const req = new userpb.CreateUserAccountWithdrawApplymentRequest();

  req.setUserId(Number(userId));
  req.setTarget(target);
  req.setAmount(amount);
  req.setRealName(realName);
  req.setIdCardNo(idCardNo);
  req.setRemark(remark);

  return new Promise((resolve, reject) => {
    cl.createUserAccountWithdrawApplyment(
      req,
      getMetadata(),
      getOptionsOfLight(),
      (err, res) => {
        if (!isNull(err)) {
          reject(wrapperGrpcError(err.details));
          return;
        }
        const opRet = res.getOpRet()!;
        if (!opRet.getSuccess()) {
          reject(wrapperGrpcError(opRet.getMsg()));
          return;
        }
        resolve(
          userAccountWithdrawApplymentPbToObject(
            res.getUserAccountWithdrawApplyment()!
          )
        );
      }
    );
  });
};


export const listUserAccountWithdrawApplyments = async (
  userId: string,
  pager: PageParams
): Promise<PagedList<UserAccountWithdrawApplyment>> => {
  const cl = getBgsUserOpSafely();
  const req = new userpb.ListUserAccountWithdrawApplymentsRequest();

  req.setLimit(pager.limit);
  req.setOffset(pager.offset);
  req.setUserId(Number(userId));

  return new Promise((resolve, reject) => {
    cl.listUserAccountWithdrawApplyments(
      req,
      getMetadata(),
      getOptionsOfLight(),
      (err, res) => {
        if (!isNull(err)) {
          reject(wrapperGrpcError(err.details));
          return;
        }
        const opRet = res.getOpRet()!;
        if (!opRet.getSuccess()) {
          reject(wrapperGrpcError(opRet.getMsg()));
          return;
        }
        const ret: PagedList<UserAccountWithdrawApplyment> = {
          total: 0,
          items: [],
        };
        ret.total = res.getTotal();
        ret.items = res.getUserAccountWithdrawApplymentsList().map(userAccountWithdrawApplymentPbToObject);
        resolve(ret);
      }
    );
  });
};

/****************************************************************************************/
/*                                   user account query                                 */
/****************************************************************************************/

export const getUserAccount = async (userId: string): Promise<UserAccount> => {
  const cl = getBgsUserOpSafely();
  const req = new userpb.GetUserAccountRequest();

  req.setUserId(Number(userId));

  return new Promise((resolve, reject) => {
    cl.getUserAccount(req, getMetadata(), getOptionsOfLight(), (err, res) => {
      if (!isNull(err)) {
        reject(wrapperGrpcError(err.details));
        return;
      }
      const opRet = res.getOpRet()!;
      if (!opRet.getSuccess()) {
        reject(wrapperGrpcError(opRet.getMsg()));
        return;
      }
      resolve(userAccountPbToObject(res.getUserAccount()!));
    });
  });
};

/****************************************************************************************/
/*                                   user account log entity                            */
/****************************************************************************************/

const userAccountLogPbToObject = (
  pb: messagepb.AccountLogInfo
): AccountLogInfo => {
  return {
    id: `${pb.getId()}`,
    logged: pbTimestampToJs(pb.getLogged())!,
    category: pb.getCategory(),
    amount: pb.getAmount(),
    subject: pb.getSubject(),
    ext_info: pb.getExtInfo(),
  };
};

/****************************************************************************************/
/*                                   user account log query                             */
/****************************************************************************************/

export const listUserAccountLogs = async (
  userId: string,
  duration: DurationParams,
  pager: PageParams
): Promise<PagedList<AccountLogInfo>> => {
  const cl = getBgsUserOpSafely();
  const req = new userpb.ListUserAccountLogsRequest();

  req.setLimit(pager.limit);
  req.setOffset(pager.offset);
  req.setTs(jsTimestampToPb(pager.ts));
  req.setBegin(jsTimestampToPb(duration.begin));
  req.setEnd(jsTimestampToPb(duration.end));
  req.setUserId(Number(userId));

  return new Promise((resolve, reject) => {
    cl.listUserAccountLogs(
      req,
      getMetadata(),
      getOptionsOfLight(),
      (err, res) => {
        if (!isNull(err)) {
          reject(wrapperGrpcError(err.details));
          return;
        }
        const opRet = res.getOpRet()!;
        if (!opRet.getSuccess()) {
          reject(wrapperGrpcError(opRet.getMsg()));
          return;
        }
        const ret: PagedList<AccountLogInfo> = {
          total: 0,
          items: [],
        };
        ret.total = res.getTotal();
        ret.items = res.getAccountLogsList().map(userAccountLogPbToObject);
        resolve(ret);
      }
    );
  });
};

export const aggrUserAccountLogsToInOut = async (
  userId: string,
  duration: DurationParams
): Promise<AggrUserAccountLogsToInOutRet> => {
  const cl = getBgsUserOpSafely();
  const req = new userpb.AggrUserAccountLogsToInOutRequest();

  req.setBegin(jsTimestampToPb(duration.begin));
  req.setEnd(jsTimestampToPb(duration.end));
  req.setUserId(Number(userId));

  return new Promise((resolve, reject) => {
    cl.aggrUserAccountLogsToInOut(
      req,
      getMetadata(),
      getOptionsOfLight(),
      (err, res) => {
        if (!isNull(err)) {
          reject(wrapperGrpcError(err.details));
          return;
        }
        const opRet = res.getOpRet()!;
        if (!opRet.getSuccess()) {
          reject(wrapperGrpcError(opRet.getMsg()));
          return;
        }
        const ret: AggrUserAccountLogsToInOutRet = {
          income: res.getIncome(),
          outlay: res.getOutlay(),
        };
        resolve(ret);
      }
    );
  });
};
